package net.posick.ws.soap;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import net.posick.ws.Constants;
import net.posick.ws.MainActivity;
import net.posick.ws.R;
import net.posick.ws.http.HTTPServerService;
import net.posick.ws.http.HTTPServerService.HTTPServerServiceBinder;

import org.apache.http.HttpException;
import org.apache.http.protocol.HttpRequestHandler;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.view.Gravity;
import android.widget.Toast;

public class SOAPServerService extends Service implements ISOAPServerService
{
    public static final String LOG_TAG = Constants.LOG_TAG + "." + SOAPServerService.class.getSimpleName();
    
    protected static final int NOTIFICATION_ID = 20140529;
    
    
    private final ISOAPServerService.Stub mBinder = new ISOAPServerService.Stub()
    {
        @Override
        public boolean isOnline()
        throws RemoteException
        {
            return SOAPServerService.this.isOnline();
        }
        
        @Override
        public Intent lookup(String pattern, String action)
        throws RemoteException
        {
            return SOAPServerService.this.lookup(pattern, action);
        }

        @Override
        public String lookupUrl(String pattern, String action)
        throws RemoteException
        {
            return SOAPServerService.this.lookupUrl(pattern, action);
        }

        @Override
        public String register(String pattern, String action, Intent intent)
        throws RemoteException
        {
            return SOAPServerService.this.register(pattern, action, intent);
        }

        @Override
        public boolean unregister(String pattern, String action)
        throws RemoteException
        {
            return SOAPServerService.this.unregister(pattern, action);
        }
    };
    
    private ServiceConnection httpServiceConnection = new ServiceConnection()
    {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service)
        {
            HTTPServerServiceBinder binder = (HTTPServerServiceBinder) service;
            httpService = binder.getService();
            connectedLatch.countDown();
            Log.i(LOG_TAG, "!!!!! HTTP Service Reference Bound !!!!!");
        }
        
        @Override
        public void onServiceDisconnected(ComponentName className)
        {
            httpService = null;
        }
    };
    
    protected CountDownLatch connectedLatch;
    
    protected HTTPServerService httpService;

    private WakeLock wakeLock;

    private WifiManager wifiManager;

    private WifiLock wifiLock;

    private PowerManager powerManager;
    

    @Override
    public IBinder asBinder()
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".asBinder()");
        return mBinder;
    }
    

    @Override
    public IBinder onBind(Intent intent)
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".onBind()");
        
        grabWakeLock(LOG_TAG);
        grabWiFiLock(LOG_TAG);
        
        return mBinder;
    }
    
    
    @Override
    @SuppressLint("InlinedApi")
    public void onCreate()
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".onCreate()");
        
        super.onCreate();
        
        connectedLatch = new CountDownLatch(1);
        
        Intent startIntent = new Intent(this, HTTPServerService.class);
        startService(startIntent);
        bindService(startIntent, httpServiceConnection, Context.BIND_IMPORTANT | Context.BIND_ABOVE_CLIENT);
        
        ComponentName service = new ComponentName(this, SOAPServerService.class);
        PackageManager pm = this.getPackageManager();
        pm.setComponentEnabledSetting(service,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
        
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                    .setSmallIcon(R.drawable.soap_launcher)
                    .setContentTitle("Web Services 4 Android")
                    .setContentText("Implementation of SOAP & RESTful Web Service Server for Android");
        
        Intent notifyIntent = new Intent(this, MainActivity.class);
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(notifyIntent);
        PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        notificationBuilder.setContentIntent(pendingIntent);
        
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = notificationBuilder.build();
        notificationManager.notify(NOTIFICATION_ID, notification);
        startForeground(NOTIFICATION_ID, notification);
    }
    
    
    @Override
    public void onDestroy()
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".onCreate()");
        
        unbindService(httpServiceConnection);
        
        super.onDestroy();
    }
    
    
    @Override
    @SuppressLint("InlinedApi")
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        Intent startIntent = new Intent(this, HTTPServerService.class);
        startService(startIntent);
        bindService(startIntent, httpServiceConnection, Context.BIND_IMPORTANT | Context.BIND_ABOVE_CLIENT);
        return START_STICKY;
    }
    
    
    @Override
    public boolean onUnbind(Intent intent)
    {
        releaseWakeLock();
        releaseWiFiLock();
        return super.onUnbind(intent);
    }


    public boolean isOnline()
    throws RemoteException
    {
        waitForHTTPService();
        return httpService != null;
    }
    
    
    public Intent lookup(String pattern, String action)
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".lookup(\"" + pattern + "\", \"" + action + "\")");
        
        Intent intent = null;
        
        waitForHTTPService();
        HTTPServerService httpService = this.httpService;
        
        if (httpService != null)
        {
            HttpRequestHandler httpHandler = httpService.lookup(pattern);
            if (httpHandler != null)
            {
                if (httpHandler instanceof SOAPRequestHandler)
                {
                    SOAPRequestHandler registeredSOAPHandler = (SOAPRequestHandler) httpHandler;
                    intent = registeredSOAPHandler.getIntent(action);
                }
            }
        }
        
        return intent;
    }
    
    
    public String lookupUrl(String pattern, String action)
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".lookupUrl(\"" + pattern + "\", \"" + action + "\")");
        
        HttpRequestHandler httpHandler = httpService.lookup(pattern);
        if (httpHandler != null && httpHandler instanceof SOAPRequestHandler)
        {
            SOAPRequestHandler soapHandler = (SOAPRequestHandler) httpHandler;
            return soapHandler.getUrl();
        } else
        {
            return null;
        }
    }


    protected void waitForHTTPService()
    {
        if (connectedLatch != null)
        {
            try
            {
                connectedLatch.await(1, TimeUnit.SECONDS);
            } catch (InterruptedException e)
            {
                // ignore
            }
        }
    }


    public String register(String pattern, String action, Intent intent)
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".register(\"" + pattern + "\", \"" + action + "\")");
        
        waitForHTTPService();
        
        try
        {
            HTTPServerService httpService = this.httpService;
            if (httpService != null)
            {
                HttpRequestHandler httpHandler = httpService.lookup(pattern);
                if (httpHandler == null)
                {
                    // Register SOAP Handler
                    SOAPRequestHandler registeredSOAPHandler = new SOAPRequestHandler(pattern, action, intent);
Log.i(LOG_TAG, getClass().getSimpleName() + " Registering Pattern \"" + pattern + "\".");
                    String url = httpService.register(pattern, registeredSOAPHandler);
Log.i(LOG_TAG, getClass().getSimpleName() + " Registering Pattern \"" + pattern + "\" registered to URL \"" + url + "\".");
                    registeredSOAPHandler.setUrl(url);
                    return url;
                } else
                {
Log.i(LOG_TAG, getClass().getSimpleName() + " Pattern \"" + pattern + "\" Already Registering.");
                    // HTTP Handler already registered
                    if (httpHandler instanceof SOAPRequestHandler)
                    {
                        // SOAP Handler already registered
                        SOAPRequestHandler registeredSOAPHandler = (SOAPRequestHandler) httpHandler;
                        Intent registeredIntent = registeredSOAPHandler.getIntent(action);
                        if (registeredIntent != null)
                        {
                            // Return registered Intent
                            intent.fillIn(registeredIntent, Intent.FILL_IN_ACTION + Intent.FILL_IN_CATEGORIES + Intent.FILL_IN_DATA);
Log.i(LOG_TAG, getClass().getSimpleName() + " Pattern \"" + pattern + "\" Already Registering to URL \"" + registeredSOAPHandler.getUrl() + "\".");
                            return registeredSOAPHandler.getUrl();
                        } else
                        {
                            // Add Action and Intent to SOAP Handler
                            registeredSOAPHandler.putIntent(action, intent);
Log.i(LOG_TAG, getClass().getSimpleName() + " Pattern \"" + pattern + "\" Already Registering to URL \"" + registeredSOAPHandler.getUrl() + "\".");
                            return registeredSOAPHandler.getUrl();
                        }
                    } else
                    {
                        Log.e(LOG_TAG, "Pattern \"" + pattern + "\" is already registered to a non-SOAP Handler.");
                        throw new HttpException("Pattern \"" + pattern + "\" is already registered to a non-SOAP Handler.");
                    }
                }
            } else
            {
                Log.e(LOG_TAG, "HTTPServer unavailable!");
                throw new HttpException("HTTPServer unavailable!");
            }
        } catch (HttpException e)
        {
            Log.e(LOG_TAG, "Error registering Intent - " + e.getMessage());
            throw new RuntimeException("Error registering Intent - " + e.getMessage());
        }
    }


    public boolean unregister(String pattern, String action)
    {
        Log.i(LOG_TAG, getClass().getSimpleName() + ".unregister(\"" + pattern + "\", \"" + action + "\")");
        
        waitForHTTPService();
        
        Intent intent = null;
        
        try
        {
            HTTPServerService httpService = this.httpService;
            if (httpService != null)
            {
                HttpRequestHandler httpHandler = httpService.lookup(pattern);
                if (httpHandler != null)
                {
                    if (httpHandler instanceof SOAPRequestHandler)
                    {
                        SOAPRequestHandler registeredSOAPHandler = (SOAPRequestHandler) httpHandler;
                        intent = registeredSOAPHandler.removeIntent(action);
                        if (registeredSOAPHandler.hasIntents())
                        {
                            httpService.unregister(pattern);
                        }
                    }
                }
            }
        } catch (HttpException e)
        {
            Log.e(LOG_TAG, "Error unregistering Intent - " + e.getMessage());
            throw new RuntimeException("Error unregistering Intent - " + e.getMessage());
        }
        
        return intent != null;
    }
    
    
    protected PowerManager powerManager()
    {
        if (powerManager == null)
        {
            powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        }
        
        // If still null display error!
        if (powerManager == null)
        {
            String message = super.getString(R.string.error_wake_lock_not_created_no_manager);
            Log.e(LOG_TAG, message);
            Toast toast = Toast.makeText(null, message, Toast.LENGTH_LONG);
            toast.setGravity(Gravity.CENTER, 0, 0);
            toast.show();
        }
        return powerManager;
    }
    
    
    protected synchronized void grabWakeLock(String tag)
    {
        if (wakeLock == null)
        {
            wakeLock = powerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
            if (wakeLock != null)
            {
                wakeLock.acquire();
            }
        }
    }
    
    
    protected synchronized void releaseWakeLock()
    {
        if (wakeLock == null)
        {
            if (wakeLock.isHeld())
            {
                wakeLock.release();
            }
            wakeLock = null;
        }
    }
    
    
    protected WifiManager wifiManager()
    {
        if (wifiManager == null)
        {
            wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        }
        
        // If still null display error!
        if (wifiManager == null)
        {
            String message = super.getString(R.string.error_wifi_lock_not_created_no_manager);
            Log.e(LOG_TAG, message);
            Toast toast = Toast.makeText(null, message, Toast.LENGTH_LONG);
            toast.setGravity(Gravity.CENTER, 0, 0);
            toast.show();
        }
        return wifiManager;
    }
    
    
    protected synchronized void grabWiFiLock(String tag)
    {
        if (wifiLock == null)
        {
            wifiLock = wifiManager().createWifiLock(WifiManager.WIFI_MODE_FULL, tag);
            if (wifiLock != null)
            {
                wifiLock.acquire();
            }
        }
    }
    
    
    protected synchronized void releaseWiFiLock()
    {
        if (wifiLock == null)
        {
            if (wifiLock.isHeld())
            {
                wifiLock.release();
            }
            wifiLock = null;
        }
    }
}
